#ifndef __tenacitas_log__file__h__
#define __tenacitas_log__file__h__


/** @file
    In this file are implemented tenacitas::log::file template class */

/** @todo  */

// ==> C++ Headers
#include <string>
#include <cstdint>
#include <sstream>
#include <fstream>
#include <memory>
#include <chrono>
#include <cstdio>
#include <utility>
#include <type_traits>

// ==> 3rds Headers
#include <boost/thread/thread.hpp>
#include <boost/shared_ptr.hpp>

// ==> Our Headers
#include "level.h"
#include <tenacitas.exception/exception.h>

// ==> Namespaces
using namespace std;

// ==> Macro Contants

// ==> Macro Commands


/** namespace of the organization */
namespace tenacitas {

    /** ==> namespace of the project */
    namespace log {

        // ==> Typedefs 

        // ==> Pre-Declarations

        // ==> Attributes 

        // ==> Functions
        
        // ==> Classes
        
        /** @p file implements a media for tenacitas::log::logger class

            The media is, as expected, a file. However, it is not decided if a
            text or binary file; the format of the messages; how is the siz of
            the file managed, etc. These decisions are left to other classes,
            implementes as type parameters.

            @tparam <b>t_writer</b> is responsible for actually writing the log
            messages to a file. It must implement:

            <tt>ptr</tt>, a type of pointer to t_writer

            <tt>static ptr create( )</tt>, which creates a pointer to a @p
            t_writer object; 

            <tt>void open_file( ofstream & p_file,
            const std::string & p_file_name ) </tt>, which opens the file @p
            p_file_name, and associates it to @p p_file 

            <tt>void close_file( ofstream & p_file ) </tt>, which closes the file
            @p p_file 

            <tt>void new_line( ofstream & p_file,
                               boost::thread:id p_thread_id,
                               level p_level,
                               const char * p_file_name,
                               const char * p_function,
                               int64_t p_line )</tt>, which starts a new line in
            the log file. @p p_file is the file where a new log line should be
            recorded; @p p_level is the level of the log message; @p p_file_name
            is the name of the file where the log message is being generated; @p
            p_function is the function where the log message is being generated;
            @p p_line is the line number in @p p_file_name where the log message
            is being generated  

            <tt>template<typename t_data>
            void write ( ofstream & p_file, const t_data & p_data )</tt>, which
            writes @p p_data to @p p_file 


            @tparam <b>t_change_file_notifier</b> is a policy that defines
            how/when logging should continue in another file. It must implement: 

            <tt>ptr</tt>, a type of pointer to t_change_file_notifier;

            <tt>static ptr create( boost::thread::id p_th_id )</tt>, which
            creates a pointer to a @p t_change_file_notifier object, based on the
            thread id informed;

            <tt>bool change_now( std::uint64_t p_size )</tt>, which informs if a
            new file should be created. To help the decision, the current file
            size is passed.


            @tparam <b>t_change_file_handler</b> is responsible for handling the
            event of changing the file where the log messages should be written
            to. It must implement:

            <tt>file_closed( )</tt> which is called when the file where the log
            messages are being written to is closed

            <tt>file_changed( )</tt> which is called when the file where the log
            messages are being written needs to be changed

            <tt>const std::string & file_name( )</tt> which retrieves the name of
            the current file

            <b>A word to the programmer user</b>
            You, dear programer, only need to worry about this class if you want
            to write your own @p file specialization.
            If you just want to use a media based on a file, you can check, for
            instance, on the tenacitas::log::text_file class.
        */
        template <typename t_writer,
                  typename t_change_file_notifier,
                  typename t_change_file_handler>
        class file {
        public:
            // ==> file - Public Internal Types

            /** Type for a pointer to @p file */
            typedef boost::shared_ptr<file> ptr;

            // ==> file - Public Constructors

            /** Destructor */
            ~file( );

            /** Creates a pointer to @p file

                @param  p_th_id is the identifier of the thread for which log
                messages will be loged in this file
            */
            static ptr create( boost::thread::id p_th_id );
            
            /** not allowed */
            file( const file & ) = delete;

            /** not allowed */
            file( file && p_r ) = delete;

            // ==> file - Public Processors

            /** Writes data to the file
                Writes any data type that to the file, according to the
                @p t_writer class

                @param  p_data is the data to be written to the file
            */
            template <typename t_data>
            void write ( const t_data & p_data );
            
            /** Starts a new line in the file
                This method is called by the tenacitas::log::logger object where
                this @p file object is defined

                @param p_thread_id is the identifier of the thread that is starting
                a new log line
                @param  p_file is the file where a new log line should be
                recorded;
                @param  p_level is the level of the log message;
                @param  p_function is the function where the log message is
                being generated;
                @param  p_line is the line number in @p p_file_name where the
                log message is being generated
            */
            void new_line( boost::thread::id p_thread_id,
                           level p_level,
                           const char * p_file,
                           const char * p_function,
                           int64_t p_line );
            
            // ==> file - Public Operators
            
            /** not-allowed */
            file & operator = ( const file & ) = delete;

            /** not-allowed */
            file & operator = ( file && p_r ) = delete;

        private:

            // ==> file - Private Internal Types

            /** Type for the change file policy*/
            typedef t_change_file_notifier change_file_notifier;

            /** Type of pointer to the @p change_file_notifier */
            typedef typename change_file_notifier::ptr change_file_notifier_ptr;

            /** Type for the line formater */
            typedef t_writer writer;

            /** Type of pointer to the line formater */
            typedef typename writer::ptr writer_ptr;

            /** */
            typedef t_change_file_handler change_file_handler;

            /** */
            typedef typename change_file_handler::ptr change_file_handler_ptr;
            

            // ==> file - Private Constructors

            /** Constructor

                @param  p_thread_id is the identifier of the thread for which
                log messages will be loged in this file
            */
            file( boost::thread::id p_thread_id );
            
            // ==> file - Private Attributes

            /** Stream where the log messages will be written to*/
            ofstream m_file;

            /** It will notify when the log file should be changed */
            change_file_notifier_ptr m_change_file_notifier;

            /** It will write log messages into the file */
            writer_ptr m_writer;

            /** It will actually do something when the file needs to be changed */
            change_file_handler_ptr m_change_file_handler;

            /** Current file size */
            uint64_t m_file_size;
        };
    }
}

// =============================================================================
//                     I M P L E M E N T A T I O N
// =============================================================================

using namespace tenacitas::log;

// -----------------------------------------------------------------------------
template <typename t_writer,
          typename t_change_file_notifier,
          typename t_change_file_handler>
file<t_writer, t_change_file_notifier, t_change_file_handler>::
~file( ) {
    // It is time to notify the change file policy, and the line formater, that
    // the thread, which is logging in this file, ended  
    try {
        m_writer->close_file( m_file );
        m_change_file_handler->file_closed( );
    }
    catch ( std::exception & l_ex ) {
        std::cerr << l_ex.what( );
    }
}

// -----------------------------------------------------------------------------
template <typename t_writer,
          typename t_change_file_notifier,
          typename t_change_file_handler>
typename file<t_writer, t_change_file_notifier, t_change_file_handler>:: ptr
file<t_writer, t_change_file_notifier, t_change_file_handler>::
create( boost::thread::id p_th_id ) {
    ptr l_printer;
    try {
        l_printer = ptr( new file( p_th_id ) );
    }
    catch ( tenacitas::exception::exception & l_ex ) {
        std::cerr << l_ex.what( );
        throw l_ex;
    }                
    catch ( std::exception & l_ex ) {
        tenacitas::exception::exception l_tenacitas_ex =
            make_tenacitas_exception( l_ex.what( ) );
        std::cerr << l_tenacitas_ex.what( );
        throw l_tenacitas_ex;
    }                
    return l_printer;
}

// -----------------------------------------------------------------------------
template <typename t_writer,
          typename t_change_file_notifier,
          typename t_change_file_handler>
template <typename t_data>
void
file<t_writer, t_change_file_notifier, t_change_file_handler>::
write ( const t_data & p_data ) {
    try {
                    
        m_writer->write( m_file, p_data );

        m_file_size += ( static_cast<uint64_t>( m_file.tellp( ) ) -
                         m_file_size ); 
    }
    catch ( tenacitas::exception::exception & l_ex ) {
        std::cerr << l_ex.what( );
        throw l_ex;
    }                
    catch ( std::exception & l_ex ) {
        tenacitas::exception::exception l_tenacitas_ex =
            make_tenacitas_exception( l_ex.what( ) );
        std::cerr << l_tenacitas_ex.what( );
        throw l_tenacitas_ex;
    }                
}

// -----------------------------------------------------------------------------
template <typename t_writer,
          typename t_change_file_notifier,
          typename t_change_file_handler>
void
file<t_writer, t_change_file_notifier, t_change_file_handler>::
new_line( boost::thread::id p_thread_id,
          level p_level,
          const char * p_file,
          const char * p_function,
          int64_t p_line ) {
    try {                    
        if ( m_change_file_notifier->change_now( m_file_size ) ) {
            m_writer->close_file( m_file );                        
            m_change_file_handler->file_changed( );
            m_writer->open_file( m_file, m_change_file_handler->file_name( ) );
        }
                
        m_writer->new_line( m_file, p_thread_id, p_level,
                            p_file, p_function, p_line );

        m_file_size += ( static_cast<uint64_t>( m_file.tellp( ) ) -
                         m_file_size );
    }
    catch ( tenacitas::exception::exception & l_ex ) {
        std::cerr << l_ex.what( );
        throw l_ex;
    }                
    catch ( std::exception & l_ex ) {
        tenacitas::exception::exception l_tenacitas_ex =
            make_tenacitas_exception( l_ex.what( ) );
        std::cerr << l_tenacitas_ex.what( );
        throw l_tenacitas_ex;
    }                                
}

// -----------------------------------------------------------------------------
template <typename t_writer,
          typename t_change_file_notifier,
          typename t_change_file_handler>
file<t_writer, t_change_file_notifier, t_change_file_handler>::
file( boost::thread::id p_thread_id )
    : m_file( ),
      m_change_file_notifier( change_file_notifier::create( ) ),
      m_writer( writer::create( ) ),
      m_change_file_handler( change_file_handler::create( p_thread_id ) ), 
      m_file_size ( 0 ) {

    try {
        m_writer->open_file( m_file, m_change_file_handler->file_name( ) );
        m_file_size = m_file.tellp( );
    }
    catch ( tenacitas::exception::exception & l_ex ) {
        std::cerr << l_ex.what( );
        throw l_ex;
    }
    catch ( std::exception & l_ex ) {
        tenacitas::exception::exception l_tenacitas_ex =
            make_tenacitas_exception( l_ex.what( ) );
        std::cerr << l_tenacitas_ex.what( );
        throw l_tenacitas_ex;
    }
}

#endif


